use crate::db::malines::KV;
use crate::db::FUDB;
///
use crate::model::kline::Kline;
use crate::model::malines::ma::LSMAv;
use crate::model::malines::ma::MAConfig;
use crate::model::malines::ma::PeriodV;
use crate::model::malines::ma::MA;
use crate::round2;
use anyhow::anyhow;
use anyhow::ensure;
use anyhow::Result;
use surrealdb::sql::Id;
use surrealdb::sql::Thing;
use yata::core::Method;
use yata::methods::LSMA;

pub struct Lsma {
    /// lsma computer for each period
    lsmas: Vec<(u8, LSMA)>,
}

impl Lsma {
    pub fn new(ma_config: &MAConfig, init_value: &f64) -> Result<Self> {
        let lsmas: Vec<(u8, LSMA)> = ma_config.periods
            .iter()
            .map(|&period| Self::create_lsma(period, init_value))
            .collect::<Result<Vec<_>, _>>()?;
        Ok(Lsma { lsmas })
    }

    pub fn next(&mut self, value: &f64) -> MA<LSMAv> {
        Self::compute(&mut self.lsmas, value)
    }

    pub fn repeat(&mut self, value: &f64) -> MA<LSMAv> {
        let mut lsmas = self.lsmas.clone();
        Self::compute(&mut lsmas, value)
    }

    fn compute(lsmas: &mut Vec<(u8, LSMA)>, value: &f64) -> MA<LSMAv> {
        for lsma in lsmas.iter_mut() {
            lsma.1.next(value);
        }

        let mas: Vec<PeriodV<LSMAv>> = lsmas
            .iter()
            .map(|(period, lsma)| PeriodV {
                period: period.clone() as u16,
                value: LSMAv {
                    tan: round2(lsma.tan()),
                    b: round2(lsma.b()),
                },
            })
            .collect();

        let ma = MA { values: mas };
        return ma;
    }

    // Function to create an LSMA instance and handle potential errors.
    pub fn create_lsma(period: u8, initial_value: &f64) -> Result<(u8, LSMA)> {
        let rs = LSMA::new(period, initial_value).map_err(|e|
            anyhow!("Failed to create LSMA: {}", e)
        );
        match rs {
            Ok(rs) => Ok((period, rs)),
            Err(e) => Err(e),
        }
    }
}

// Function to create an LSMA instance and handle potential errors.
fn create_lsma(period: u8, initial_value: &f64) -> Result<(u8, LSMA)> {
    let rs = LSMA::new(period, initial_value).map_err(|e| anyhow!("Failed to create LSMA: {}", e));
    match rs {
        Ok(rs) => Ok((period, rs)),
        Err(e) => Err(e),
    }
}
/// 更新指定K线数据的lsma线性回归趋势线,指定MA步长,现在只能做全量计算
///
/// @param klines: K线数据
///
/// config: lsma设置
///
/// return 更新的记录数
///
pub async fn update_lsma(klines: &Vec<Kline>, config: &Option<MAConfig>) -> Result<u32> {
    ensure!(klines.len() > 0, "klines is empty");

    let mut updates = 0u32;

    if let Some(fstk) = klines.first() {
        let mut ma_values: Vec<KV<MA<LSMAv>>> = Vec::new();
        let ma_config = config.as_ref().cloned().unwrap_or_default();

        let mut lsmas: Vec<(u8, LSMA)> = ma_config.periods
            .iter()
            .map(|&period| create_lsma(period, &fstk.close))
            .collect::<Result<Vec<_>, _>>()?;

        for (i, kline) in klines.iter().enumerate() {
            if let Some(Thing { id: Id::String(kid_str), .. }) = &kline.id {
                for lsma in lsmas.iter_mut() {
                    lsma.1.next(&kline.close);
                }

                let mas: Vec<PeriodV<LSMAv>> = lsmas
                    .iter()
                    .map(|(period, lsma)| PeriodV {
                        period: period.clone() as u16,
                        value: LSMAv {
                            tan: round2(lsma.tan()),
                            b: round2(lsma.b()),
                        },
                    })
                    .collect();

                let ma = MA { values: mas };

                println!("kline[{i}] close={} ==> {:?}", kline.close, ma);

                // let updated: Option<FudaRecord> = FUDB
                //     .update(("kline", kid_str))
                //     .patch(PatchOp::replace("/lsma", ma))
                //     .await
                //     .map_err(|err| anyhow!(err))?;
                ma_values.push(KV {
                    id: kline.id.clone().unwrap(),
                    value: ma,
                });
                // if let Some(_) = updated {
                updates += 1;
                // }
            }
        }

        //TODO, use surrealdb Statement For loop
        let query = FUDB.query(
            r#"FOR $kv IN $ma_values { UPDATE $kv.id SET l_lsma = $kv.value ;}"#
        ).bind(("ma_values", ma_values.clone()));

        // println!(
        //     "(ema::update_ema) query={:?} \n\nma_values={:?}\n",
        //     query, ma_values
        // );
        let rs = query.await?;
        // println!("(ema::update_ema) rs= {:?}", rs);
    }

    Ok(updates)
}

#[cfg(test)]
mod test {
    use crate::db;
    use crate::model::kline::Klt;
    use crate::model::kline::KLINE_COMMON_FIELDS;
    use crate::model::malines::ma::MAConfig;

    #[tokio::test]
    async fn test_update_lsma() {
        let _ = db::init().await.unwrap();
        let lsma_config = Some(MAConfig {
            ..Default::default()
        });

        let klt_vec = vec![
            Klt::M005,
            Klt::M015,
            Klt::M030,
            Klt::M060,
            Klt::DAY,
            Klt::WEEK,
            Klt::MONTH
        ];
        for klt in &klt_vec {
            let klines = db::kline
                ::select("SZ.KZZ.128136", None, Some(klt), Some("klts ASC"), 0, 10000).await
                .unwrap();
            let result = db::malines::lsma::update_lsma(&klines, &lsma_config).await;
            assert_eq!(result.is_ok(), true);
        }

        let result = db::kline::select(
            "SZ.KZZ.128136",
            None,
            Some(&Klt::M005),
            Some("klts ASC"),
            0,
            100
        ).await;
        if let Ok(klines) = &result {
            for (index, kline) in klines.iter().enumerate() {
                if index > 3 {
                    break;
                }
                // println!(
                //     "\n[{:02}], close:{:.2}=>lsma:{:?}",
                //     index, kline.close, kline.malines,
                // );
            }
        }
    }
}
